﻿
//	------------------------------------------------------------------------------
//                                       _                          
//              _ __ ___   ___ _ __ ___ | |__  _ __ __ _ _ __   ___ 
//             | '_ ` _ \ / _ \ '_ ` _ \| '_ \| '__/ _` | '_ \ / _ \
//             | | | | | |  __/ | | | | | |_) | | | (_| | | | |  __/
//             |_| |_| |_|\___|_| |_| |_|_.__/|_|  \__,_|_| |_|\___|
//                                                     
//	------------------------------------------------------------------------------

#region - License -
//
//	------------------------------------------------------------------------------                                                   
//	         Copyright (c) 2010 Peter Vyvey (peter.vyvey@chiliware.be)
//	------------------------------------------------------------------------------
//
//
//	This file is part of CHILIWARE MEMBRANE.
//
//	CHILIWARE MEMBRANE is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Lesser General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	CHILIWARE MEMBRANE is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public License
//	along with CHILIWARE MEMBRANE.  If not, see <http://www.gnu.org/licenses/>.
//
//
#endregion

#region - Changes -
//
//	peter.vyvey@chiliware.be		2010-08-10		Created
//
#endregion

#region - Using -

using System;
using System.Collections.Generic;
using System.Threading;
using Chiliware.Membrane.ExceptionManagement;
using Chiliware.Membrane.Pattern.Creational;
using Chiliware.Membrane.Security;
using Chiliware.Membrane.Security.Authentication;
using Chiliware.Membrane.Model;
using Chiliware.Membrane.Extension;
using System.Collections.Concurrent;

#endregion

namespace Chiliware.Membrane.Model
{
	#region - ApplicationModel.Portal -

	public sealed partial class ApplicationModel
	{
		#region - Nested classes: ApplicationModel.Portal -

		/// <summary>
        /// Portal for external client application to the application and session scoped services.
        /// </summary>
        public sealed class Portal
        {
            #region - Constructors -

            /// <summary>
            /// Default constructor.
            /// </summary>
            internal Portal() 
				: base() 
            {
                this.InitializeObject();
            }

            #endregion

            #region - Private fields -

            /// <summary>
            /// A dictionary containing the mapping between the portal context identifier and the application session.
            /// </summary>
			private readonly ConcurrentDictionary<string, Guid> sessionRegistry = new ConcurrentDictionary<string, Guid>();

            #endregion

            #region - Properties -

            /// <summary>
            /// Container for the thread local portal context.
            /// </summary>
            public ThreadLocal<string> Context { get; private set; }

            #endregion

            #region - Public methods -

            /// <summary>
            /// Creates a new session.
            /// </summary>
            /// <param name="credentials">The user credentials.</param>
			/// <returns>The session parameters.</returns>
			public Dictionary<string, string> CreateSession(Credential credentials)
			{
				credentials.CatchNullArgument("credentials");

				// Reset the current thread session ID.
				Singleton<ApplicationModel>.Instance.threadSessionID.Value = Guid.Empty;

				// Initialize the CONTEXT value.
				this.Context.Value = Guid.NewGuid().ToString();

				// Try to retrieve the session for this context, NONE should exist.
				Guid sessionID = Guid.Empty;
				if (!this.sessionRegistry.TryGetValue(this.Context.Value, out sessionID))
				{
					sessionID = Singleton<ApplicationModel>.Instance.CreateSession(credentials);
					this.sessionRegistry[this.Context.Value] = sessionID;
				}
				else
				{
					throw new SecurityException(ExceptionNumber.DuplicateSession);
				}

				return Singleton<ApplicationModel>.Instance.GetSessionParameters();
			}

            /// <summary>
            /// Retrieves the requested service.
            /// </summary>
            /// <typeparam name="T">The type of the requested service.</typeparam>
            /// <param name="context">The portal context identifier.</param>
            /// <returns>An instance of the requested service.</returns>
			public T GetService<T>(string context) 
				where T : IApplicationService, new()
			{
				context.CatchNullOrEmptyArgument("context");

				// Reset the current thread session ID.
				Singleton<ApplicationModel>.Instance.threadSessionID.Value = Guid.Empty;
				this.Context.Value = context;

				Guid sessionID;
				if (this.sessionRegistry.TryGetValue(context, out sessionID))
				{
					Singleton<ApplicationModel>.Instance.SessionID = sessionID;
				}
				else
				{
					throw new SecurityException(ExceptionNumber.InvalidContext);
				}

				return Singleton<ApplicationModel>.Instance.GetService<T>();
			}

			/// <summary>
			/// Stores a data object to the given storage level.
			/// </summary>
			/// <typeparam name="T">The type of the data object.</typeparam>
			/// <param name="context">The portal context identifier.</param>
			/// <param name="key">The key for the data object.</param>
			/// <param name="data">The data object.</param>
			/// <param name="storageLevel">The storage level.</param>
			public void StoreUserData<T>(string context, string key, T data, UserProfileStorageLevelType storageLevel)
			{
				context.CatchNullOrEmptyArgument("context");

				// Reset the current thread session ID.
				Singleton<ApplicationModel>.Instance.threadSessionID.Value = Guid.Empty;
				this.Context.Value = context;

				Guid sessionID;
				if (this.sessionRegistry.TryGetValue(context, out sessionID))
				{
					Singleton<ApplicationModel>.Instance.SessionID = sessionID;
				}
				else
				{
					throw new SecurityException(ExceptionNumber.InvalidContext);
				}

				Singleton<ApplicationModel>.Instance.StoreUserData(key, data, storageLevel);
			}

			/// <summary>
			/// Retrieves the data object with the given key from the given storage level.
			/// </summary>
			/// <typeparam name="T">The type of the data object.</typeparam>
			/// <param name="context">The portal context identifier.</param>
			/// <param name="key">The key for the data object.</param>
			/// <param name="storageLevel">The storage level.</param>
			/// <returns>The data object of type T.</returns>
			public T RetrieveUserData<T>(string context, string key, UserProfileStorageLevelType storageLevel)
			{
				context.CatchNullOrEmptyArgument("context");

				// Reset the current thread session ID.
				Singleton<ApplicationModel>.Instance.threadSessionID.Value = Guid.Empty;
				this.Context.Value = context;

				Guid sessionID;
				if (this.sessionRegistry.TryGetValue(context, out sessionID))
				{
					Singleton<ApplicationModel>.Instance.SessionID = sessionID;
				}
				else
				{
					throw new SecurityException(ExceptionNumber.InvalidContext);
				}

				return Singleton<ApplicationModel>.Instance.RetrieveUserData<T>(key, storageLevel);
			}

            /// <summary>
            /// Destroys a session.
            /// </summary>
            /// <param name="context">The portal context identifier.</param>
			public void DestroySession(string context)
			{
				context.CatchNullOrEmptyArgument("context");

				// Reset the current thread session ID.
				Singleton<ApplicationModel>.Instance.threadSessionID.Value = Guid.Empty;
				this.Context.Value = context;

				try
				{
					Guid sessionID;
					if (this.sessionRegistry.TryGetValue(context, out sessionID))
					{
						Singleton<ApplicationModel>.Instance.SessionID = sessionID;
					}
					else
					{
						throw new SecurityException(ExceptionNumber.InvalidContext);
					}

					Singleton<ApplicationModel>.Instance.DestroySession();
				}
				finally
				{
					Guid sessionID;
					this.sessionRegistry.TryRemove(context, out sessionID);
					this.Context.Value = string.Empty;
				}
			}

            #endregion

            #region - Private & protected methods -

            private void InitializeObject()
            {
                this.Context = new ThreadLocal<string>(() => string.Empty);
            }

            #endregion
        }

        #endregion
    }

    #endregion
}
